package icasue.base.config.filter;

import icasue.base.utils.wareable.CLog;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.boot.autoconfigure.AutoConfigurationImportFilter;
import org.springframework.boot.autoconfigure.AutoConfigurationMetadata;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.Arrays;
import java.util.Collection;
import java.util.Objects;
import java.util.Optional;

/**
 * @Author: Qiao Hang
 * @CreateDate: 2021/5/26 下午5:36
 * @UpdateDate:
 * @Description: 该过滤器用于提供候选组件的筛选能力，针对我们的 @ElCondition注解 / 元数据配置的装载条件 进行候选组件的筛选!
 */
@Slf4j
abstract class FilterElCondition extends SpringBootCondition implements AutoConfigurationImportFilter, BeanFactoryAware, BeanClassLoaderAware, EnvironmentAware {

    private static final String EL_CONDITION = "ElCondition";
    protected static BeanFactory beanFactory;
    protected static ClassLoader classLoader;
    protected static Environment environment;
    private final ExpressionParser parser = new SpelExpressionParser();

    @Override
    public boolean[] match(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
        boolean[] matchResult = checkItemCondition(autoConfigurationClasses, autoConfigurationMetadata);
        return matchResult;
    }

    private boolean[] checkItemCondition(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata){
        boolean[] removeUnits = new boolean[autoConfigurationClasses.length];

        for (int i = 0; i < autoConfigurationClasses.length; i++) {
            if(autoConfigurationClasses[i] == null){
                continue;
            }
            Class autoConfigCls = null;
            try {
                autoConfigCls = classLoader.loadClass(autoConfigurationClasses[i]);
            }catch (Throwable e){
                try {
                    autoConfigCls = Class.forName(autoConfigurationClasses[i]);
                } catch (Throwable ex) {
                    /*Class Not Found, current class will be ignored.*/
                }
            }
            if(autoConfigCls == null){
                removeUnits[i] = true;
                continue;
            }

            Collection<String> metaDataElConditions = null;
            String metaDataElConditionsStr = autoConfigurationMetadata.get(autoConfigurationClasses[i], EL_CONDITION);
            if(!Strings.isEmpty(metaDataElConditionsStr)){
                metaDataElConditions = Arrays.asList(metaDataElConditionsStr.split(",,"));
            }
            //优先级: 元数据 > 注解
            if(CollectionUtils.isEmpty(metaDataElConditions)){
                metaDataElConditions = touchAnnotationValues(autoConfigCls);
            }
            //存在el表达式时，进行el表达式解析，只要有一条不通过则标记 remove,
            //如果通过则不处理此前filter的标记结果.
            Optional<Boolean> markFailed = doElValid(autoConfigCls.getName(), metaDataElConditions);
            if(markFailed.isPresent()){
                removeUnits[i] = markFailed.get().booleanValue();
            }
        }

        boolean[] result = new boolean[removeUnits.length];
        System.arraycopy(removeUnits,0,result,0,removeUnits.length);

        //reverse.
        for (int i = 0; i < result.length; i++) {
            result[i] = !result[i];
        }
        return result;
    }

    protected Optional<Boolean> doElValid(String componentDesc, Collection<String> elConditions){
        Optional<Boolean> markFailed = Optional.ofNullable(null);
        //存在el表达式时，进行el表达式解析，只要有一条不通过则标记 remove
        if(!CollectionUtils.isEmpty(elConditions)){
            StandardEvaluationContext elContext = new StandardEvaluationContext();
            elContext.setRootObject(null);
            elContext.setVariable("environment",environment);
            elContext.setVariable("classLoader",classLoader);
            elContext.setVariable("beanFactory",beanFactory);
            for (String metaDataElCondition : elConditions) {
                Boolean value = Boolean.FALSE;
                try {
                    Expression expression = parser.parseExpression(metaDataElCondition);
                    value = expression.getValue(elContext, Boolean.class);
                }catch (Throwable e){
                    e.printStackTrace();
                    value = Boolean.FALSE;
                }
                if(!value.booleanValue()){
                    CLog.info("########### Component description defined on [" + Objects.toString(componentDesc)+ "] ignore to init, " +
                            "reason: \n\t el-expression: [ " + metaDataElCondition + " ] check failed.");
                    markFailed = Optional.ofNullable(Boolean.TRUE);
                    break;
                }
            }
        }
        return markFailed;
    }


    private Collection<String> touchAnnotationValues(Class autoConfigCls){
        ElCondition annotationsByType = null;
        try {
            annotationsByType = (ElCondition) autoConfigCls.getDeclaredAnnotation(ElCondition.class);
        }catch (ArrayStoreException securityError){ }
        if(annotationsByType != null){
            String[] value = annotationsByType.value();
            if(value.length > 0){
                return Arrays.asList(value);
            }
        }
        return null;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        FilterElCondition.beanFactory = beanFactory;
    }

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        FilterElCondition.classLoader = classLoader;
    }

    @Override
    public void setEnvironment(Environment environment) {
        FilterElCondition.environment = environment;
    }
}
